Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Link Navigation relative to current Environment #24483

Merged
merged 7 commits into from
Aug 17, 2023

Conversation

Talha345
Copy link
Contributor

@Talha345 Talha345 commented Aug 12, 2023

Details

Fixed Issues

$ #22523
PROPOSAL: #22523 (comment)

Tests

Test Case 1:

  1. Go to any chat and send the following links in the chat:
  1. Now click on all the links one be one, only the link corresponding to the current environment should be opened internally whereas all other links should be opened externally.

Test Case 2:

  1. Go to any chat and send the following links in the chat:
  1. Now click on all the links one be one.
    In Dev: All 3 links should be external.
    In STG: 2nd link should be internal
    in PROD: Last link should be internal

This test case ensures that if an extra www subdomain is added to any URL, it is discarded and the correct behaviour prevails.

Note: In Dev ENV, you can change the line https://github.com/Expensify/App/compare/main...Talha345:fix/22523?expand=1#diff-810e7a5e5e8692ea5ea69e3b593e7e6abb3992e2682a7aaf89c8615db6840d83R22 and set it to const environmentURL = CONST.STAGING_NEW_EXPENSIFY_URL; or const environmentURL = CONST.NEW_EXPENSIFY_URL; to test the behaviour in STG and PROD environments on localhost.

  • Verify that no errors appear in the JS console

Offline tests

Same as above

QA Steps

  1. Go to any chat and send the following links in the chat:
  1. Now click on all the links one be one, only the staging links should open internally and all other links should open externally.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android / native
    • Android / Chrome
    • iOS / native
    • iOS / Safari
    • MacOS / Chrome / Safari
    • MacOS / Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that the left part of a conditional rendering a React component is a boolean and NOT a string, e.g. myBool && <MyComponent />.
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • If we are not using the full Onyx data that we loaded, I've added the proper selector in order to ensure the component only re-renders when the data it is using changes
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG))
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
web.mov
Mobile Web - Chrome
WhatsApp.Video.2023-08-12.at.2.57.19.PM.mp4
Mobile Web - Safari
safari.mov
Desktop

Since the desktop app runs on port 8083 so only localhost links with port 8083 will be treated as internal.

desktop.mov
iOS

Since the DEV_PORT returned in native apps is 8082, only localhost links with port 8082 will be treated as internal on IOS and Android.

ios.mov
Android
android.mov

@Talha345 Talha345 requested a review from a team as a code owner August 12, 2023 13:21
@melvin-bot melvin-bot bot requested review from situchan and removed request for a team August 12, 2023 13:21
@melvin-bot
Copy link

melvin-bot bot commented Aug 12, 2023

@situchan Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@@ -80,7 +80,7 @@ export default {
},
CAPTURE_METRICS: lodashGet(Config, 'CAPTURE_METRICS', 'false') === 'true',
ONYX_METRICS: lodashGet(Config, 'ONYX_METRICS', 'false') === 'true',
DEV_PORT: process.env.PORT || 8080,
DEV_PORT: process.env.PORT || 8082,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated since we now run dev on 8082 so fallback port should be 8082.

@@ -15,7 +14,12 @@ jest.doMock('react-native', () => {
// runs against index.native.js source and so anything that is testing a component reliant on withWindowDimensions()
// would be most commonly assumed to be on a mobile phone vs. a tablet or desktop style view. This behavior can be
// overridden by explicitly setting the dimensions inside a test via Dimensions.set()
let dimensions = CONST.TESTING.SCREEN_SIZE.SMALL;
let dimensions = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to make these changes because this mock file was importing CONST file which was in turn importing src/libs/Url.js file in which we have imported the polyfill. Since the polyfill import internally imports RN packages and since we are in test environment the packages were being used before mocking them (which is done later in this file) and the test suite broke.To fix this we have multiple options:

  1. We can import the polyfill in the root App.js file as mentioned in the usage guidelines (https://github.com/charpeni/react-native-url-polyfill) but it will then override URL and URLSearchParams classes in the entire project.I actually committed that change in the previous commit and everything was working fine including the test cases.Also these 2 classes are not used much so the chance of breaking something is low and since the overrides basically extends the default RN classes so I think we are fine using this option too.

  2. Remove the import from the test suite before the mocking happens.In this option I had 2 further options:

    • Remove the Url.js import from CONST.js as it was imported to use the addTrailingForwardSlash just at one place in the file.Also importing lib files in CONST file does not make sense.
    • The other option was to remove the CONST file import in this file and that's what I have done now.

Let me know what you think is the best way forward.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the CONST file import in this file

Current solution you did works for me.
@francoisl what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah works for me too.

@@ -224,8 +9,8 @@ describe('Url', () => {
it('It should work correctly with www in both urls', () => {
expect(Url.hasSameExpensifyOrigin('https://www.new.expensify.com/inbox/124', 'https://www.new.expensify.com/action/123')).toBe(true);
});
it('It should work correctly without https://', () => {
expect(Url.hasSameExpensifyOrigin('new.expensify.com/action/1234', 'new.expensify.com/action/123')).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this case was removed? It should still work without https://

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL href attributes always need to have a protocol in them otherwise it is an invalid URL.If you type google.com in any chat on STG and then hover over it to view the href it will show https://google.com, so our logic already creates the correct href

Any url without the protocol and // is treated as a relative URL and is not spec complaint.You can go to any browser and type new URL('google.com') in the console section and it will throw an error.

Therefore, the previous test case was invalid.

@@ -1,221 +1,6 @@
const Url = require('../../src/libs/Url');

describe('Url', () => {
describe('getURLObject()', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these example urls should still be unit tested in getPathFromURL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added tests for getPathFromURL

@@ -15,7 +14,12 @@ jest.doMock('react-native', () => {
// runs against index.native.js source and so anything that is testing a component reliant on withWindowDimensions()
// would be most commonly assumed to be on a mobile phone vs. a tablet or desktop style view. This behavior can be
// overridden by explicitly setting the dimensions inside a test via Dimensions.set()
let dimensions = CONST.TESTING.SCREEN_SIZE.SMALL;
let dimensions = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the CONST file import in this file

Current solution you did works for me.
@francoisl what do you think?

@Talha345 Talha345 requested a review from situchan August 13, 2023 20:04
@Talha345
Copy link
Contributor Author

@situchan @francoisl Any updates on PR review?

@situchan
Copy link
Contributor

@Talha345 please pull main. Unit Tests are now fixed in main

@Talha345
Copy link
Contributor Author

@Talha345 please pull main. Unit Tests are now fixed in main

Done!

@Talha345
Copy link
Contributor Author

@situchan Bump for review!

@situchan
Copy link
Contributor

I am not able to get dev/staging environmentURL on iOS. We now support flavors and I tested with all 3 apps. All are pointed to production https://new.expensify.com/.
@Talha345 how did you test iOS?

@Talha345
Copy link
Contributor Author

Talha345 commented Aug 15, 2023

how did you test iOS?

@situchan
I simply ran npm run ios to install dev version and tested locally.Have also attached video above.

How can i install multiple versions?

You can simply set the environment by setting the ENV variable ENVIRONMENT=(development|adhoc|production).Setting the value as adhoc will also return the staging url.Other values are self explanatory.

@situchan
Copy link
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • Android / native
    • Android / Chrome
    • iOS / native
    • iOS / Safari
    • MacOS / Chrome / Safari
    • MacOS / Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that the left part of a conditional rendering a React component is a boolean and NOT a string, e.g. myBool && <MyComponent />.
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
web-staging.mov
Mobile Web - Chrome
mchrome-dev.mov
Mobile Web - Safari
msafari-dev.mov
Desktop
desktop.mov
desktop2.mov
iOS
ios-dev.mov
ios-prod.mov
Android
android-dev.mov

Copy link
Contributor

@situchan situchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :shipit:
@francoisl all yours

@melvin-bot melvin-bot bot requested a review from francoisl August 16, 2023 07:37
@francoisl francoisl merged commit e043331 into Expensify:main Aug 17, 2023
13 of 14 checks passed
@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@ahmedGaber93
Copy link
Contributor

Hi @situchan @francoisl @Talha345
while working in #23143 I found
this PR in this condition open any link with the same origin in the same tab with URL path without params.

if (internalNewExpensifyPath && hasSameOrigin) {
Navigation.navigate(internalNewExpensifyPath);
return;
}

But the URL params is required to attachment URL.
So if I type attachment URL in chat and try to open it, it not work without params.

Steps to reproduce

  1. Open a chat and send an attachment.
  2. Open the attachment preview and copy the URL then send it in the chat.
  3. Click the URL you send it in step 2.

You will see the copied URL:

 http://localhost:8082/r/592589459564169/attachment?source=/chat-attachments/2323255261707990908/w_27c02e267d4a31ceb507e27614174b78309fd003.png

is open wrong link without params

// not complete link
http://localhost:8082/r/592589459564169/attachment

I think we need to exclude the attachment URLs from this condition or add new condition previous this condition to open the attachment URLs with full URL

issue is reported in slack here by @ayazhussain79

@Talha345
Copy link
Contributor Author

Talha345 commented Aug 17, 2023

I type attachment URL in chat and try to ope

Hi @ahmedGaber93,

I see what you mean. We updated how the attrPath was calculated in this PR and I assumed that we are only using pretty URLs therefore did not add the query params to the path.

This can be resolved easily by updating getPathFromURL method to return query params too like this:

function getPathFromURL(url) {
    try {
        const parsedUrl = new URL(url);
        const path = parsedUrl.pathname + parsedUrl.search;
        return path.substring(1); // Remove the leading '/'
    } catch (error) {
        console.error('Error parsing URL:', error);
        return ''; // Return empty string for invalid URLs
    }
}

@situchan @francoisl Where should I update this change? Create a new PR or what?

@francoisl
Copy link
Contributor

Yes, can you make a new PR please?

@situchan
Copy link
Contributor

bump @Talha345

@Talha345
Copy link
Contributor Author

bump @Talha345

@situchan

PR is done.Just stuck due to long delay in building android app locally.

PR will be ready for review once it is done.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/francoisl in version: 1.3.56-0 🚀

platform result
🤖 android 🤖 failure ❌
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/roryabraham in version: 1.3.56-24 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 failure ❌
🕸 web 🕸 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants